package upload_task

import (
	"bytes"
	"crypto/sha256"
	"crypto/tls"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"hash"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"time"

	"gitee.com/baidugoogle/upload_file/upload_task/structs"
)

const PartSize = 16 * 1024 * 1024

type GetToken func() string

type UploadTask struct {
	uri string
	f   GetToken
}

func NewUploadTask(uri string, f GetToken) *UploadTask {
	return &UploadTask{
		uri: uri,
		f:   f,
	}
}

func (t *UploadTask) getURL(path string) string {
	return t.uri + "/bvnru/v1" + path
}

func (t *UploadTask) getHeaders(kvs ...map[string]string) map[string]string {
	h := map[string]string{
		"Authorization": t.f(),
	}

	for _, kv := range kvs {
		for k, v := range kv {
			h[k] = v
		}
	}

	return h
}

type jsonUploadTaskInfo struct {
	cli        *http.Client
	reader     io.ReaderAt
	filename   string
	uploadId   string
	partNumber int
	partCount  int
	partSize   int // const
}

// Upload 上传文件
func (t *UploadTask) Upload(filename string, info *structs.JsonCreateExt) (string, error) {

	cli := newHttpClient()

	var request = structs.JsonCreateRequest{
		PartSize: PartSize,
		Extra:    info,
	}

	fp, err := os.Open(filename)
	if err != nil {
		log.Printf("open file fail. %v [%v]", err, filename)
		return "", err
	} else {
		defer fp.Close()
	}

	// 准备
	{

		// 分页大小
		if fileStat, err := fp.Stat(); err != nil {
			return "", err
		} else {
			request.Name = fileStat.Name()
			request.PartCount = structs.PageCount(int(fileStat.Size()), PartSize)
			request.Size = int(fileStat.Size())
		}

		// 预哈希
		if true {
			buffer := make([]byte, 1024)
			_, _ = io.LimitReader(fp, 1024).Read(buffer)
			request.PreHash = structs.SHA256(buffer)

			_, _ = fp.Seek(0, io.SeekStart)
		}

		// 调试, 上传添加整个文件的Hash
		if false {
			if h, err := structs.SHA256File(filename); err != nil {
				log.Printf("SHA256File fail. %v [%v]", err, filename)
				return "", err
			} else {
				request.ContentHash = hex.EncodeToString(h)
			}
		}
	}

	// 创建上传任务
	objectCreateReply, err := t.httpCreate(cli, t.getURL("/fileUpload/create"), &request)
	if err != nil {
		log.Printf("httpCreate fail. %v [%v]", err, filename)
		return "", err
	}

	switch objectCreateReply.FileState {
	case structs.FileStateFileIdMatched, structs.FileStateContentMatched:
		// 文件匹配, 文件已存在, 秒传
		// 已完成, 结束
		log.Printf("秒传 %v [%v]", objectCreateReply.FileState, filename)
		return "", nil
	case structs.FileStatePreHashMatched:
		// 预哈希匹配, 下一步匹配文件哈希
		// 测试完整Hash
		//   1. 相同, 结束
		//   2. 不相同, 上传
		if h, err := structs.SHA256File(filename); err != nil {
			log.Printf("SHA256File fail. %v [%v]", err, filename)
			return "", err
		} else {
			request.ContentHash = hex.EncodeToString(h)
		}

		// 获取文件哈希, 判断是否可以秒传
		if objectCreateReply, err = t.httpCreate(cli, t.getURL("/fileUpload/create"), &request); err != nil {
			return "", err
		} else {
			switch objectCreateReply.FileState {
			case structs.FileStateContentMatched:
				// 文件内容匹配, 文件已存在, 秒传
				// 已完成, 结束
				log.Printf("秒传 [%v]", filename)
				return "", nil
			case structs.FileStateNewFile:
				// 新文件case
				// 上传
			default:
				return "", fmt.Errorf("unknown file state %v", objectCreateReply.FileState)
			}
		}

	case structs.FileStateNewFile:
		// 新文件case
		// 上传
	default:
		return "", fmt.Errorf("unknown file state %v", objectCreateReply.FileState)
	}

	if objectCreateReply.NewFile == nil {
		return "", fmt.Errorf("error. new file is nil")
	}

	uploadId := objectCreateReply.NewFile.UploadId

	log.Printf("upload part count %v [%v]", request.PartCount, filename)
	now := time.Now()
	for i := 0; i < request.PartCount; i++ {
		partNumber := i

		//log.Printf("upload part: %v %v [%v]", uploadId, partNumber, filename)
		// 上传分片数据
		if reply, err := t.uploadPart(&jsonUploadTaskInfo{
			cli:        cli,
			reader:     fp,
			filename:   filename,
			uploadId:   uploadId,
			partNumber: partNumber,
			partCount:  request.PartCount,
			partSize:   PartSize,
		}); err != nil {
			log.Printf("uploadPart fail, %v [%v]", err, filename)
			return uploadId, err
		} else {
			_ = reply.Code
		}
	}
	log.Printf("耗时: %v", time.Since(now))

	log.Printf("check complete [%v]", filename)

	if false {
		// 检查是否所有都上传完成
		if allComplete, err := t.checkComplete(cli, uploadId, filename); err != nil {
			return uploadId, err
		} else if !allComplete {
			return uploadId, fmt.Errorf("have un complete part")
		}
	}

	log.Printf("complete [%v]", filename)
	// 上传完成
	reply, err := t.httpComplete(cli, uploadId, filename)
	if err != nil {
		log.Printf("[结束上传] httpComplete fail. %v [%v] [%v]", err, filename, reply.Duration)
		return uploadId, err
	} else {
		log.Printf("[结束上传] httpComplete result. [%v] [%v] %v %v", filename, reply.Duration, reply.Code, reply.Msg)
	}

	return uploadId, nil
}

// ResumeUpload 文件续传
func (t *UploadTask) ResumeUpload(filename string, uploadId string) error {
	cli := newHttpClient()

	// 获取获取分片状态
	listUploadPartReply, err := t.httpListUploadedParts(cli, uploadId, filename)
	if err != nil {
		return err
	}

	fp, err := os.Open(filename)
	if err != nil {
		log.Printf("open file fail. %v [%v]", err, filename)
		return err
	} else {
		defer fp.Close()
	}

	partSize := listUploadPartReply.Data.PartSize

	// 检查未完成的分片, 上传
	for _, part := range listUploadPartReply.Data.UploadedParts {
		if part.Complete {
			continue
		}

		// 上传分片数据
		if reply, err := t.uploadPart(&jsonUploadTaskInfo{
			cli:        cli,
			reader:     fp,
			filename:   filename,
			uploadId:   uploadId,
			partNumber: part.PartNumber,
			partCount:  len(listUploadPartReply.Data.UploadedParts),
			partSize:   partSize,
		}); err != nil {
			log.Println(err)
			return err
		} else {
			_ = reply.Code
		}
	}

	// 检查是否所有都上传完成
	if allComplete, err := t.checkComplete(cli, uploadId, filename); err != nil {
		return err
	} else if !allComplete {
		return fmt.Errorf("have un complete part")
	}

	// 上传完成
	reply, err := t.httpComplete(cli, uploadId, filename)
	if err != nil {
		log.Printf("httpComplete fail, %v [%v]", err, filename)
		return err
	} else {
		_ = reply
	}

	return nil
}

// 检查是否所有都上传完成
func (t *UploadTask) checkComplete(cli *http.Client, uploadId string, filename string) (bool, error) {
	var (
		result = false
		err    error
	)
	for i := 0; i < 5; i++ {
		if reply, e := t.httpListUploadedParts(cli, uploadId, filename); e != nil {
			log.Printf("httpListUploadedParts fail. %v [%v]", e, filename)
			result = false
			err = e
			continue
		} else {
			result = true
			err = nil
			for _, part := range reply.Data.UploadedParts {
				if !part.Complete {
					// 有没有上传完成的
					result = false
				}
			}

		}
	}

	return result, err
}

// 上传分片数据
func (t *UploadTask) uploadPart(info *jsonUploadTaskInfo) (*structs.JsonPutPartReply, error) {

	var (
		reader     io.ReaderAt = info.reader
		filename   string      = info.filename
		uploadId   string      = info.uploadId
		partNumber             = info.partNumber
		partCount  int         = info.partCount
		partSize   int         = info.partSize
	)

	// 读取分片数据
	r := io.NewSectionReader(reader, int64(partNumber*partSize), int64(partSize)) // io.LimitReader(fp, int64(PartSize))

	return t.httpPut(info.cli, uploadId, partNumber, partCount, r, filename)
}

// 创建上传任务
func (t *UploadTask) httpCreate(cli *http.Client, uri string, request *structs.JsonCreateRequest) (*structs.JsonCreateReply, error) {
	log.Println("[http create]")
	body, err := json.Marshal(request)
	if err != nil {
		return nil, err
	}

	reply, duration, err := structs.HttpRequest(cli, http.MethodPost, uri, bytes.NewBuffer(body), "application/json; charset=utf-8", t.getHeaders())
	if err != nil {
		return nil, err
	} else {
		log.Println(structs.FormatJsonString(reply))
	}

	var objectCreateReply structs.JsonCreateReply
	if err = json.Unmarshal(reply, &objectCreateReply); err != nil {
		log.Println(err)
		return nil, err
	} else if objectCreateReply.Code != structs.BVResultSuccess {
		return &objectCreateReply, fmt.Errorf("create fail. %v %v", objectCreateReply.Code, objectCreateReply.Msg)
	}

	objectCreateReply.Duration = duration
	return &objectCreateReply, nil
}

// 上传分片
func (t *UploadTask) httpPut(cli *http.Client, uploadId string, partNumber, partCount int, r io.Reader, filename string) (*structs.JsonPutPartReply, error) {
	var (
		uri = fmt.Sprintf(t.getURL("/fileUpload/put/%v/%v"), uploadId, partNumber)

		partFilename = fmt.Sprintf("%v", partNumber)
		reply        []byte
		err          error
	)
	_ = partFilename

	// log.Printf(">>> [upload...] %v(%v/%v) hash: %v, [%v]", uploadId, partNumber, partCount, partHash, filename)
	if false {
		reply, _, err = structs.HttpRequest(cli, http.MethodPut, uri, r, "application/octet-stream", map[string]string{
			"Content-Hash": "",
		})
	} else {
		hr := NewHookReader(r)
		data, _ := io.ReadAll(hr)
		partHash := hr.SHA256()
		reply, err = structs.NewMultipartFormData(cli, uri, nil, "file", partFilename, data, t.getHeaders(map[string]string{
			"Content-Hash": partHash,
		}))
	}
	if err != nil {
		log.Printf("NewMultipartFormData fail. %v [%v]", err, filename)
		return nil, err
	} else {
		//log.Println(structs.FormatJsonString(reply))
	}

	var objectPutReply structs.JsonPutPartReply
	if err = json.Unmarshal(reply, &objectPutReply); err != nil {
		//objectPutReply.Duration = duration
		log.Printf("Unmarshal fail. %v [%v]", err, filename)
		return nil, err
	} else if objectPutReply.Code != structs.BVResultSuccess {
		//objectPutReply.Duration = duration
		return &objectPutReply, fmt.Errorf("%v", objectPutReply.Msg)
	}

	//objectPutReply.Duration = duration

	return &objectPutReply, nil
}

// 完成
func (t *UploadTask) httpComplete(cli *http.Client, uploadId string, filename string) (*structs.JsonCompleteReply, error) {
	log.Printf("http complete [%v]", filename)

	var objectReply structs.JsonCompleteReply

	uri := fmt.Sprintf(t.getURL("/fileUpload/complete/%v"), uploadId)
	reply, duration, err := structs.HttpRequest(cli, http.MethodPost, uri, nil, "application/json; charset=utf-8", t.getHeaders())
	if err != nil {
		log.Printf("HttpRequest fail. %v [%v]", err, filename)
		objectReply.Duration = duration
		return &objectReply, err
	} else {
		log.Printf("HttpRequest result. [%v] %v", filename, structs.FormatJsonString(reply))
	}

	if err = json.Unmarshal(reply, &objectReply); err != nil {
		log.Printf("Unmarshal fail. %v [%v]", err, filename)
		objectReply.Duration = duration
		return &objectReply, err
	} else if objectReply.Code != structs.BVResultSuccess {
		objectReply.Duration = duration
		return &objectReply, fmt.Errorf("%v", objectReply.Msg)
	}

	objectReply.Duration = duration
	return &objectReply, nil
}

// HttpCancel 取消
func (t *UploadTask) HttpCancel(uploadId string) (*structs.JsonCancelReply, error) {
	log.Println("[http cancel]")
	cli := newHttpClient()

	uri := fmt.Sprintf(t.getURL("/fileUpload/cancel/%v"), uploadId)
	reply, duration, err := structs.HttpRequest(cli, http.MethodPost, uri, nil, "application/json; charset=utf-8", t.getHeaders())
	if err != nil {
		log.Println(err)
		return nil, err
	} else {
		log.Println(structs.FormatJsonString(reply))
	}

	var objectReply structs.JsonCancelReply
	if err = json.Unmarshal(reply, &objectReply); err != nil {
		log.Println(err)
		objectReply.Duration = duration
		return &objectReply, err
	} else if objectReply.Code != structs.BVResultSuccess {
		objectReply.Duration = duration
		return &objectReply, fmt.Errorf("%v", objectReply.Msg)
	}

	objectReply.Duration = duration
	return &objectReply, nil
}

// 列举上传任务
func (t *UploadTask) httpListUpload() (*structs.JsonListUploader, error) {
	log.Println("[http list upload task]")
	cli := newHttpClient()

	uri := fmt.Sprintf(t.getURL("/fileUpload/listUpload"))
	reply, duration, err := structs.HttpRequest(cli, http.MethodPost, uri, nil, "application/json; charset=utf-8", t.getHeaders())
	if err != nil {
		log.Println(err)
		return nil, err
	} else {
		log.Println(structs.FormatJsonString(reply))
	}

	var objectReply structs.JsonListUploader
	if err = json.Unmarshal(reply, &objectReply); err != nil {
		log.Println(err)

		objectReply.Duration = duration
		return &objectReply, err
	} else if objectReply.Code != structs.BVResultSuccess {

		objectReply.Duration = duration
		return &objectReply, fmt.Errorf("%v", objectReply.Msg)
	}

	objectReply.Duration = duration
	return &objectReply, nil
}

// 获取分片状态
func (t *UploadTask) httpListUploadedParts(cli *http.Client, uploadId string, filename string) (*structs.JsonListUploadedPartsReply, error) {
	log.Println("[http list upload parts]")

	uri := fmt.Sprintf(t.getURL("/fileUpload/listUploadedParts/%v"), uploadId)
	reply, duration, err := structs.HttpRequest(cli, http.MethodPost, uri, nil, "application/json; charset=utf-8", t.getHeaders())
	if err != nil {
		log.Println(err)
		return nil, err
	} else {
		// log.Printf("分片状态: [%v] %v", filename, structs.FormatJsonString(reply))
	}

	var objectReply structs.JsonListUploadedPartsReply
	if err = json.Unmarshal(reply, &objectReply); err != nil {
		log.Printf("Unmarshal fail. %v [%v]", err, filename)
		objectReply.Duration = duration
		return &objectReply, err
	} else if objectReply.Code != structs.BVResultSuccess {
		objectReply.Duration = duration
		return &objectReply, fmt.Errorf("%v", objectReply.Msg)
	}

	objectReply.Duration = duration
	return &objectReply, nil
}

func newHttpClient() *http.Client {
	dialer := &net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
	}

	cli := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
			DialContext:           dialer.DialContext,
			MaxIdleConns:          256,
			MaxIdleConnsPerHost:   16,
			ResponseHeaderTimeout: time.Minute,
			IdleConnTimeout:       time.Minute,
			TLSHandshakeTimeout:   10 * time.Second,
			ExpectContinueTimeout: 10 * time.Second,
			// Set this value so that the underlying transport round-tripper
			// doesn't try to auto decode the body of objects with
			// content-encoding set to `gzip`.
			//
			// Refer:
			//    https://golang.org/src/net/http/transport.go?h=roundTrip#L1843
			DisableCompression: true,
		},
	}

	return cli
}

type hookReader struct {
	r io.Reader
	h hash.Hash
}

func NewHookReader(r io.Reader) *hookReader {
	return &hookReader{
		r: r,
		h: sha256.New(),
	}
}

func (tis *hookReader) SHA256() string {
	return hex.EncodeToString(tis.h.Sum(nil))
}

func (tis *hookReader) Read(p []byte) (n int, err error) {
	n, err = tis.r.Read(p)
	if n > 0 {
		tis.h.Write(p[:n])
	}
	return n, err
}
